www.gusucode.com > 有意思的HTML5 圆形钢琴,演奏出动人乐章源码程序 > 有意思的HTML5 圆形钢琴,演奏出动人乐章/html5svgpiano/html5-svg-piano/js/index.js

    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
const group = document.createElementNS("http://www.w3.org/2000/svg", "g")
const input = {}
const value = {}
Array.from(document.querySelectorAll(".ctrl input")).forEach(inp => {
  let val
  const name = inp.name
  input[name] = inp
  inp.addEventListener('input', change)
  inp.addEventListener('change', () => inp.value = val)
  change()

  function change() {
    val = Math.min(+inp.max, Math.max(+inp.value, +inp.min))
    const step = +inp.step || 1
    val = Math.round(val / step) * step
    value[name] = val
  }
})
const diam1 = 150
const diam2 = 215
const diam3 = 300
const whites = [0, null, 1, null, 2, 3, null, 4, null, 5, null, 6]
const keyNumber = new WeakMap()
let octaveCount = 3
let octaveLoop = 3

input.loop.addEventListener('input', function() {
  value.repeat = input.repeat.value = 1
})
group.classList.add("keyboard")

function angle(turn) {
  const rad = turn * Math.PI * 2
  const cos = Math.cos(rad)
  const sin = Math.sin(rad)
  return {
    rad,
    cos,
    sin
  }
}

function sharpPos(note) {
  if (note <= 5)
    return note * 3 / 7 / 5
  else
    return 3 / 7 + (note - 5) * 4 / 7 / 7
}

function draw() {
  octaveLoop = value.loop
  octaveCount = octaveLoop * input.repeat.value
  for (let octave = 0; octave < octaveCount; octave++) {
    for (let i = 0; i < 12; i++) {
      const white = whites[i]
      const a1 = angle((octave + sharpPos(i)) / octaveCount)
      const a2 = angle((octave + sharpPos(i + 1)) / octaveCount)
      const elm = document.createElementNS("http://www.w3.org/2000/svg", "path")
      if (i === 0) {
        elm.classList.add('ut')
        if (octave % octaveLoop === 0) {
          elm.classList.add('first-ut')
        }
      }
      let path = `\
M ${a1.cos * diam2} ${a1.sin * diam2}\
L ${a1.cos * diam3} ${a1.sin * diam3}\
A ${diam3} ${diam3} 0 0 1 ${a2.cos * diam3},${a2.sin * diam3}\
L ${a2.cos * diam2},${a2.sin * diam2}`
      if (white == null) {
        path += `A ${diam2} ${diam2} 0 0 0 ${a1.cos * diam2} ${a1.sin * diam2}`
        elm.classList.add("sharp")
      } else {
        const a0 = angle((octave + (white / 7)) / octaveCount)
        const a3 = angle((octave + ((white + 1) / 7)) / octaveCount)
        path += `\
A ${diam2} ${diam2} 0 0 1 ${a3.cos * diam2},${a3.sin * diam2}\
L ${a3.cos * diam1},${a3.sin * diam1}\
A ${diam1} ${diam1} 0 0 0 ${a0.cos * diam1},${a0.sin * diam1}\
L ${a0.cos * diam2},${a0.sin * diam2}\
A ${diam2} ${diam2} 0 0 1 ${a1.cos * diam2},${a1.sin * diam2}`
      }
      elm.setAttribute("d", path)
      group.appendChild(elm)
      keyNumber.set(elm, (i + octave * 12) % (12 * octaveLoop))
    }
  }
  svg.appendChild(group)
  document.body.appendChild(svg)
}
draw()
input.loop.addEventListener("input", draw)
input.repeat.addEventListener("input", draw)

function resize() {
  const vw = window.innerWidth
  const vh = window.innerHeight
  const min = Math.min(vw, vh)
  const scale = min / diam3 / 2
  const transform = `translate(${vw / 2}, ${vh / 2}) scale(${scale})`
  group.setAttribute('transform', transform)
}
resize()
window.addEventListener("resize", resize)
group.addEventListener('mouseover', e => play(keyNumber.get(e.target)))
group.addEventListener('mouseleave', stop)

const ac = new AudioContext()
const volume = ac.createGain()
let wave
let oldNote = null
volume.gain.value = 0
volume.connect(ac.destination)
const osc = ac.createOscillator()

function createWave() {
  const max = 13
  const real = new Float32Array(Math.pow(2, max))
  for (let i = 0; i < max; i += octaveLoop) {
    real[Math.pow(2, i)] = 1
    real[Math.pow(2, i) * 3] = value.fifth
    real[Math.pow(2, i) * 5] = value.third
  }
  wave = ac.createPeriodicWave(real, real)
  osc.setPeriodicWave(wave)
}
input.loop.addEventListener("input", createWave)
input.fifth.addEventListener("input", createWave)
input.third.addEventListener("input", createWave)
createWave()
osc.connect(volume)
osc.start()

function play(note) {
  volume.gain.value = .3
  if (oldNote === null || value.slide === 0) {
    const freq = 440 * (Math.pow(2, (note - 9) / 12)) / 32
    osc.frequency.setValueAtTime(freq, ac.currentTime)
    oldNote = note
    return
  }
  const note2 = note
  if (oldNote !== null) {
    while (note - oldNote > 6)
      note -= 12
    while (note - oldNote < -6)
      note += 12
  }
  const oldFreq = 440 * (Math.pow(2, (oldNote - 9) / 12)) / 32
  const freq = 440 * (Math.pow(2, (note - 9) / 12)) / 32
  osc.frequency.cancelScheduledValues(ac.currentTime)
  osc.frequency.setValueAtTime(oldFreq, ac.currentTime)
  osc.frequency.linearRampToValueAtTime(freq, ac.currentTime + value.slide)
  oldNote = note2
}

function stop() {
  volume.gain.value = 0
  oldNote = null
}